1 module backtraced;
2 
3 version(Windows) version = UseBacktraced;
4 else version(linux) version = UseBacktraced;
5 
6 version(UseBacktraced):
7 debug:
8 import core.demangle;
9 import std.algorithm.searching;
10 
11 version (Windows)
12 {
13 
14     pragma(lib, "dbghelp.lib");
15     import core.sys.windows.windows;
16     import core.sys.windows.dbghelp;
17     import core.stdc.stdlib : free, calloc;
18     import core.stdc.stdio : fprintf, stderr;
19     import core.stdc.string : memcpy, strncmp, strlen;
20     import std.string;
21 
22     struct SYMBOL_INFO
23     {
24         ULONG SizeOfStruct;
25         ULONG TypeIndex;
26         ULONG64[2] Reserved;
27         ULONG Index;
28         ULONG Size;
29         ULONG64 ModBase;
30         ULONG Flags;
31         ULONG64 Value;
32         ULONG64 Address;
33         ULONG Register;
34         ULONG Scope;
35         ULONG Tag;
36         ULONG NameLen;
37         ULONG MaxNameLen;
38         CHAR[1] Name;
39     }
40 
41     extern (Windows) USHORT RtlCaptureStackBackTrace(ULONG FramesToSkip, ULONG FramesToCapture, PVOID* BackTrace, PULONG BackTraceHash);
42     extern (Windows) BOOL SymFromAddr(HANDLE hProcess, DWORD64 Address, PDWORD64 Displacement, SYMBOL_INFO* Symbol);
43     extern (Windows) BOOL SymGetLineFromAddr64(HANDLE hProcess, DWORD64 dwAddr, PDWORD pdwDisplacement, IMAGEHLP_LINEA64* line);
44 
45 
46     debug void printStackTrace()
47     {
48         // enum MAX_DEPTH = 256;
49         // void*[MAX_DEPTH] stack;
50 
51         // HANDLE process = GetCurrentProcess();
52         // ushort frames = RtlCaptureStackBackTrace(0, MAX_DEPTH, stack.ptr, null);
53         // SYMBOL_INFO* symbol = cast(SYMBOL_INFO*) calloc((SYMBOL_INFO.sizeof) + 256 * char.sizeof, 1);
54         // symbol.MaxNameLen = 255;
55         // symbol.SizeOfStruct = SYMBOL_INFO.sizeof;
56 
57         // IMAGEHLP_LINEA64 line = void;
58         // line.SizeOfStruct = SYMBOL_INFO.sizeof;
59 
60         // DWORD dwDisplacement;
61         // 
62         // static int ends_with(const(char)* str, const(char)* suffix)
63         // {
64         //     if (!str || !suffix)
65         //         return 0;
66         //     size_t lenstr = strlen(str);
67         //     size_t lensuffix = strlen(suffix);
68         //     if (lensuffix > lenstr)
69         //         return 0;
70         //     return strncmp(str + lenstr - lensuffix, suffix, lensuffix) == 0;
71         // }
72 
73         // for (uint i = 0; i < frames; i++)
74         // {
75         //     SymFromAddr(process, cast(DWORD64)(stack[i]), null, symbol);
76         //     SymGetLineFromAddr64(process, cast(DWORD64)(stack[i]), &dwDisplacement, &line);
77 
78         //     // auto f = frames - i - 1;
79         //     char[] funcName = demangle(symbol.Name.ptr[0..symbol.NameLen]);
80         //     auto fname = line.FileName;
81         //     auto lnum = line.LineNumber;
82 
83         //     if (ends_with(fname, __FILE__) || funcName.canFind("rt.dmain2._d_run_main2"))
84         //         continue; // skip trace from this module
85 
86         //     fprintf(stderr, "%s:%i - %.*s\n", fname, lnum, cast(int)funcName.length, funcName.ptr);
87         // }
88         
89         // free(symbol);
90     }
91     extern (Windows) LONG TopLevelExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo)
92     {
93         debug
94         {
95             import hip.util.conv;
96             throw new Exception("Caught Exception (0x"~toHex(pExceptionInfo.ExceptionRecord.ExceptionCode)~")");
97         }
98         return EXCEPTION_CONTINUE_SEARCH;
99     }
100 
101     extern (C) export void backtraced_Register()
102     {
103         debug
104         {
105             SymInitialize(GetCurrentProcess(), null, true);
106             SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_DEBUG);
107             SetUnhandledExceptionFilter(&TopLevelExceptionHandler);
108         }
109     }
110 }
111 
112 version (linux)
113 {
114     import core.stdc.signal : SIGSEGV, SIGFPE, SIGILL, SIGABRT, signal;
115     import core.stdc.stdlib : free, exit;
116     import core.stdc.string : strlen, memcpy;
117     import core.stdc.stdio : fprintf, stderr, sprintf, fgets, fclose, FILE;
118     import core.sys.posix.unistd : STDERR_FILENO, readlink;
119     import core.sys.posix.signal : SIGUSR1;
120     import core.sys.posix.stdio : popen, pclose;
121     import core.sys.linux.execinfo : backtrace, backtrace_symbols;
122     import core.sys.linux.dlfcn : dladdr, dladdr1, Dl_info, RTLD_DL_LINKMAP;
123     import core.sys.linux.link : link_map;
124     import core.demangle : demangle;
125 
126     extern (C) export void backtraced_Register()
127     {
128         signal(SIGSEGV, &handler);
129         signal(SIGUSR1, &handler);
130     }
131 
132     // TODO: clean this mess
133     // TODO: use core.demangle instead
134     extern (C) void handler(int sig) nothrow @nogc
135     {
136         enum MAX_DEPTH = 32;
137 
138         string signal_string;
139         switch (sig)
140         {
141         case SIGSEGV:
142             signal_string = "SIGSEGV";
143             break;
144         case SIGFPE:
145             signal_string = "SIGFPE";
146             break;
147         case SIGILL:
148             signal_string = "SIGILL";
149             break;
150         case SIGABRT:
151             signal_string = "SIGABRT";
152             break;
153         default:
154             signal_string = "unknown";
155             break;
156         }
157 
158         fprintf(stderr, "-------------------------------------------------------------------+\r\n");
159         fprintf(stderr, "Received signal '%s' (%d)\r\n", signal_string.ptr, sig);
160         fprintf(stderr, "-------------------------------------------------------------------+\r\n");
161 
162         void*[MAX_DEPTH] trace;
163         int stack_depth = backtrace(&trace[0], MAX_DEPTH);
164         char** strings = backtrace_symbols(&trace[0], stack_depth);
165 
166         enum BUF_SIZE = 1024;
167         char[BUF_SIZE] syscom = 0;
168         char[BUF_SIZE] my_exe = 0;
169         char[BUF_SIZE] output = 0;
170 
171         readlink("/proc/self/exe", &my_exe[0], BUF_SIZE);
172 
173         fprintf(stderr, "executable: %s\n", &my_exe[0]);
174         fprintf(stderr, "backtrace: %i\n", stack_depth);
175 
176         for (auto i = 2; i < stack_depth; ++i)
177         {
178             auto line = strings[i];
179             auto len = strlen(line);
180             bool insideParenthesis;
181             int startParenthesis;
182             int endParenthesis;
183             for (int j = 0; j < len; j++)
184             {
185                 // ()
186                 if (!insideParenthesis && line[j] == '(')
187                 {
188                     insideParenthesis = true;
189                     startParenthesis = j + 1;
190                 }
191                 else if (insideParenthesis && line[j] == ')')
192                 {
193                     insideParenthesis = false;
194                     endParenthesis = j;
195                 }
196             }
197             auto addr = convert_to_vma(cast(size_t) trace[i]);
198             FILE* fp;
199 
200             auto locLen = sprintf(&syscom[0], "addr2line -e %s %p | ddemangle", &my_exe[0], addr);
201             fp = popen(&syscom[0], "r");
202 
203             auto loc = fgets(&output[0], output.length, fp);
204             fclose(fp);
205 
206             // printf("loc: %s\n", loc);
207 
208             auto getLen = strlen(output.ptr);
209 
210             char[256] func = 0;
211             memcpy(func.ptr, &line[startParenthesis], (endParenthesis - startParenthesis));
212             sprintf(&syscom[0], "echo '%s' | ddemangle", func.ptr);
213             fp = popen(&syscom[0], "r");
214 
215             output[getLen - 1] = ' '; // strip new line
216             auto locD = fgets(&output[getLen], cast(int)(output.length - getLen), fp);
217             fclose(fp);
218 
219             fprintf(stderr, "%s", output.ptr);
220         }
221         exit(-1);
222     }
223 
224     // https://stackoverflow.com/questions/56046062/linux-addr2line-command-returns-0/63856113#63856113
225     size_t convert_to_vma(size_t addr) nothrow @nogc
226     {
227         Dl_info info;
228         link_map* link_map;
229         dladdr1(cast(void*) addr, &info, cast(void**)&link_map, RTLD_DL_LINKMAP);
230         return addr - link_map.l_addr;
231     }
232 }